// internal/service/impl.go
package service

import (
	"context"
	"errors"
	"log"
	"math/rand"
	"time"

	"URLshortener/internal/dao/cache"
	"URLshortener/internal/dao/sql"
	"URLshortener/internal/model"
)

type urlServiceImpl struct {
	dao         sql.UrlDao  // 数据操作对象,给的是一个接口类型，封装其内部方法，避免直接通过连接池对象操作
	cache       cache.Cache // 缓存操作对象
	maxAttempts int         // 生成唯一短码的最大尝试次数
}

func NewUrlService(dao sql.UrlDao, cache cache.Cache) UrlService {
	return &urlServiceImpl{dao: dao, cache: cache, maxAttempts: 5}
}

func (s *urlServiceImpl) CreateShortUrl(ctx context.Context, originalUrl string, expireHours int) (string, error) {
	// 检查原始URL是否已存在,若存在就将短码返回，并返回重复错误
	if url, exists, _ := s.dao.FindByOriginalUrl(ctx, originalUrl); exists {
		return "your short code: " + url.ShortCode, ErrUrlExists
	}

	// 生成唯一短码
	code, err := s.generateUniqueCode(ctx)
	if err != nil {
		return "", err
	}

	// 保存到数据库
	if err := s.dao.SaveUrl(ctx, &model.Url{
		OriginalUrl: originalUrl,
		ShortCode:   code,
		ExpireTime:  time.Now().Add(time.Duration(expireHours) * time.Minute),
	}); err != nil {
		return "", err
	}
	// 异步更新缓存
	go func() {
		cacheKey := code
		if err := s.cache.Set(context.Background(), cacheKey, originalUrl, time.Duration(expireHours)*time.Minute); err != nil {
			log.Println(err)
		}
	}()

	return code, nil
}

func (s *urlServiceImpl) CreateCustomShortUrl(ctx context.Context, originalUrl string, customCode string, expireHours int) error {
	// 检查短码是否可用
	exists, err := s.dao.ExistShortCode(ctx, customCode)
	if err != nil {
		return err
	}
	if exists {
		return ErrShortCodeConflict
	}

	return s.dao.SaveUrl(ctx, &model.Url{
		OriginalUrl: originalUrl,
		ShortCode:   customCode,
		ExpireTime:  time.Now().Add(time.Duration(expireHours) * time.Hour),
		IsCustom:    true,
	})
}

func (s *urlServiceImpl) GetOriginalUrl(ctx context.Context, shortCode string) (string, error) {
	cacheKey := shortCode
	// 先查缓存
	if url, err := s.cache.Get(context.Background(), cacheKey); err == nil {
		return url, nil
	}

	// 缓存未命中，查询sql数据库
	url, err := s.dao.FindByShortCode(ctx, shortCode)
	if err != nil {
		return "", err
	}

	// 检查链接是否过期
	if time.Now().After(url.ExpireTime) {
		cacheDeleteErr := s.cache.Delete(context.Background(), url.ShortCode)
		if cacheDeleteErr != nil {
			log.Println(cacheDeleteErr)
		}
		daoDeleteErr := s.dao.DeleteUrl(context.Background(), url.ShortCode)
		if daoDeleteErr != nil {
			log.Println(daoDeleteErr)
		}
		return "", ErrUrlExpired
	}
	// 未过期
	// 更新缓存
	go func() {
		if err := s.cache.Set(context.Background(), url.ShortCode, url.OriginalUrl, time.Until(url.ExpireTime)); err != nil {
			log.Println(err)
		}
	}()
	return url.OriginalUrl, nil
}

func (s *urlServiceImpl) GetUrlDetails(ctx context.Context, shortCode string) (*model.Url, error) {
	return s.dao.FindByShortCode(ctx, shortCode)
}

func (s *urlServiceImpl) DeleteShortUrl(ctx context.Context, shortCode string) error {
	return s.dao.DeleteUrl(ctx, shortCode)
}

// 私有方法：生成唯一短码
func (s *urlServiceImpl) generateUniqueCode(ctx context.Context) (string, error) {
	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

	for i := 0; i < s.maxAttempts; i++ {
		code := make([]byte, 6)
		for j := range code {
			code[j] = charset[rand.Intn(len(charset))]
		}
		shortCode := string(code)

		if exists, _ := s.dao.ExistShortCode(ctx, shortCode); !exists {
			return shortCode, nil
		}
	}
	return "", ErrCodeGenerationFailed
}

// 错误定义
var (
	ErrUrlExists            = errors.New("URL already exists")
	ErrShortCodeConflict    = errors.New("short code conflict")
	ErrUrlExpired           = errors.New("URL expired")
	ErrCodeGenerationFailed = errors.New("failed to generate unique code")
)
